<?php

namespace App\Service;

use Exception;
use GdImage;
use JetBrains\PhpStorm\Pure;

/**
 * 生成海报
 *
 * Class Poster
 */
class Poster
{
    /** @var resource 当前操作资源对象 */
    protected $background;

    /** @var array 资源集 */
    protected array $resources = [];

    /** @var array 色彩 */
    protected array $colors = [];

    /** @var string 字体文件 */
    protected string $fontFile = '';

    /**
     * Poster constructor.
     *
     * @param string $fontFile 字体文件
     */
    public function __construct(string $fontFile)
    {
        $this->fontFile = $fontFile;
    }


    /**
     * 创建空白画图
     *
     * @param int $weigh 宽度
     * @param int $height 高度
     * @param string $fontFile 字体文件
     * @return Poster
     * @throws Exception
     */
    #[Pure]
    public static function Create(int $weigh, int $height, string $fontFile): Poster
    {
        $resource = new self($fontFile);

        $resource->background = imagecreatetruecolor($weigh, $height);
        $resource->resources[] = $resource->background;

        return $resource;
    }

    /**
     * 通过图片创建画布
     *
     * @param string $backgroundImage 图片地址
     * @param string $fontFile 字体文件路径
     * @return Poster
     * @throws Exception
     */
    public function build(string $backgroundImage, string $fontFile = ''): Poster
    {
        $this->background = $this->resource($backgroundImage);

        if ($fontFile !== '') {
            $this->fontFile = $fontFile;
        }

        return $this;
    }

    /**
     * 初始化画布
     *
     * @param int $width 所占宽度
     * @param int $height 所占高度
     * @param string $fontFile 字体文件路径
     * @return Poster
     * @throws Exception
     */
    public function initialize(int $width, int $height, string $fontFile = ''): Poster
    {
        $this->background = $this->resource("{$width},{$height}");

        if ($fontFile !== '') {
            $this->fontFile = $fontFile;
        }

        return $this;
    }

    /**
     * 获取图像资源
     *
     * @param string $image 图片地址或者画布大小字符串
     * @return GdImage
     * @throws Exception
     */
    protected function resource(string $image): GdImage
    {
        $size = explode(',', $image);
        if (count($size) === 2) {
            $resource = imagecreatetruecolor($size[0], $size[1]);
        } else {
            if (preg_match('/^https?:\/\//i', $image) || @is_file($image)) {
                $image = file_get_contents($image);
            }
            $resource = imagecreatefromstring($image);
        }

        if ($resource === false) {
            throw new Exception('从' . $image . '新建图像失败');
        }

        $this->resources[] = $resource;

        return $resource;
    }

    /**
     * 图像圆形处理
     *
     * @param GdImage $resource 图像资源
     * @return false|resource
     * @throws Exception
     */
    protected function circle(GdImage $resource): GdImage|bool
    {
        //创建透明画布
        $width = $height = min(imagesx($resource), imagesy($resource));
        $dstImg = $this->resource($width . ',' . $height);
        imagesavealpha($dstImg, true);
        $transparent = imagecolorallocatealpha($dstImg, 255, 255, 255, 127);
        imagefill($dstImg, 0, 0, $transparent);
        //填充图片中在圆内的点到透明画布
        $radius = $width / 2;
        for ($x = 0; $x < $width; $x++) {
            for ($y = 0; $y < $height; $y++) {
                if ((pow($x - $radius, 2) + pow($y - $radius, 2)) < pow($radius, 2)) {
                    imagesetpixel($dstImg, $x, $y, imagecolorat($resource, $x, $y));
                }
            }
        }

        return $dstImg;
    }

    /**
     * 设置图像
     *
     * @param string $image 本地、网络、二进制图片
     * @param int $x 起始x坐标
     * @param int $y 起始y坐标
     * @param int $width 所占宽度
     * @param int $height 所占高度
     * @param bool $circle 是否需要转成圆形
     * @return Poster
     * @throws Exception
     */
    public function image(string $image, int $x, int $y, int $width, int $height, bool $circle = false): Poster
    {
        $dstImg = $this->resource($image);
        $circle && $dstImg = $this->circle($dstImg);
        imagecopyresampled($this->background, $dstImg, $x, $y, 0, 0, $width, $height, imagesx($dstImg), imagesy($dstImg));

        return $this;
    }

    /**
     * 设置文字
     *
     * @param string $text 文本
     * @param float $size 字体大小
     * @param int $x 起始x坐标(注意是字体左下角的坐标)
     * @param int $y 起始y坐标
     * @param int $fontBold 字体加粗 1标准 值越大加粗越明显
     * @param string $rgb rgb颜色字符串 逗号隔开
     * @param ?string $fontFamily 字体
     * @return Poster
     * @throws Exception
     */
    public function text(string $text, float $size, int $x, int $y, int $fontBold = 1, string $rgb = '255,255,255', string $fontFamily = null): Poster
    {
        if (empty($fontFamily) || !file_exists($fontFamily)) {
            $fontFamily = $this->fontFamily();
        }

        $fontBold = abs($fontBold);
        for ($i = 0; $i < $fontBold; $i++) {
            imagettftext($this->background, $size, 0, $x, $y, $this->color($rgb), $fontFamily, $text);
        }

        return $this;
    }

    /**
     * 设置线条
     *
     * @param int $x1 第1个点x坐标
     * @param int $y1 第1个点y坐标
     * @param int $x2 第2个点x坐标
     * @param int $y2 第2个点y坐标
     * @param string $rgb rgb颜色字符串 逗号隔开
     * @param int $weight 线条粗细
     * @return Poster
     * @throws Exception
     */
    public function line(int $x1, int $y1, int $x2, int $y2, string $rgb = '255,255,255', int $weight = 1): Poster
    {
        imagesetthickness($this->background, $weight);
        imageline($this->background, $x1, $y1, $x2, $y2, $this->color($rgb));

        return $this;
    }

    /**
     * 获取颜色
     *
     * @param string $rgb rgb颜色字符串 逗号隔开
     * @return false|int
     * @throws Exception
     */
    protected function color(string $rgb): bool|int
    {
        if (isset($this->colors[$rgb])) {
            return $this->colors[$rgb];
        }
        $rgbArr = explode(',', $rgb);
        if (count($rgbArr) !== 3) {
            throw new Exception('rgb颜色格式错误');
        }
        $color = imagecolorallocate($this->background, $rgbArr[0], $rgbArr[1], $rgbArr[2]);
        $this->colors[$rgb] = $color;

        return $color;
    }

    /**
     * 设置三色
     *
     * @param int $red
     * @param int $green
     * @param int $blue
     * @return Poster
     */
    public function setColor(int $red = 255, int $green = 255, int $blue = 255): Poster
    {
        $color = imagecolorallocate($this->background, $red, $green, $blue);
        imagefill($this->background, 0, 0, $color);

        $this->colors = [$red, $green, $blue];

        return $this;
    }

    /**
     * 用颜色填充区域
     *
     * @param int $x 开始位置
     * @param int $y 开始位置
     * @param int $width 填充宽度
     * @param int $height 填充高度
     * @param array $rgb 三色 [red, green, blue]
     * @param int $alpha [0, 127]
     * @return Poster
     */
    public function fillArea(int $x, int $y, int $width, int $height, array $rgb, int $alpha = 0): Poster
    {
        // 创建区域，填充颜色
        $area = imagecreate($width, $height);
        $color = imagecolorallocatealpha($area, $rgb[0], $rgb[1], $rgb[2], $alpha);

        imagefill($area, $x, $y, $color);

        imagecopyresampled($this->background, $area, $x, $y, 0, 0, $width, $height, imagesx($area), imagesy($area));

        imagedestroy($area);

        return $this;
    }

    /**
     * 填充边框
     *
     * @param int $x
     * @param int $y
     * @param int $width
     * @param int $height
     * @param array $rgb
     * @param int $borderWidth
     * @return Poster
     * @throws Exception
     */
    public function coverBorder(int $x, int $y, int $width, int $height, array $rgb, int $borderWidth): Poster
    {
        if ($x - $borderWidth < 0 || $y - $borderWidth < 0) {
            throw new Exception('设置失败，检查填充长度！');
        }
        $this->fillArea($x, $y, $width, $height, $rgb);

        $this->fillArea(
            $x + $borderWidth + 1,
            $y + $borderWidth + 1,
            $width - (2 * $borderWidth + 1),
            $height - (2 * $borderWidth + 1),
            $this->colors
        );

        return $this;
    }

    /**
     * 字体
     *
     * @return string
     */
    protected function fontFamily(): string
    {
        return $this->fontFile;
    }

    /**
     * 输出
     *
     * @param null $filename 保存图片名称
     */
    public function output($filename = null)
    {
        $ext = $filename ? pathinfo($filename, PATHINFO_EXTENSION) : 'jpg';
        switch ($ext) {
            case 'gif':
                $function = 'imagegif';
                $mime = 'image/gif';
                break;
            case 'png':
                $function = 'imagepng';
                $mime = 'image/png';
                break;
            case 'jpg':
            case 'jpeg':
            default:
                $function = 'imagejpeg';
                $mime = 'image/jpeg';
                break;
        }

        if (is_null($filename)) {
            header('Content-type:' . $mime);
            call_user_func($function, $this->background);
        } else {
            call_user_func($function, $this->background, $filename);
        }
    }

    public function __destruct()
    {
        foreach ($this->resources as $resource) {
            imagedestroy($resource);
        }
    }
}


